namespace ImapX
{
    using System;
    using System.Collections;
    using System.Text.RegularExpressions;
    using System.Diagnostics;
    using System.Linq;
    using Parsing.DataItems;
    using Parsing.Commands;
    using Parsing.Tokens;
    using System.Collections.Generic;
    using System.Threading;

    public class Imap : ImapBase
    {
        internal char _delimiter;
        private FolderCollection _folders;

        public Imap()
        {
            this._delimiter = '/';
        }

        public Imap(string host, int port, bool useSsl)
        {
            this._delimiter = '/';
            base._imapHost = host;
            base._imapPort = port;
            base._useSSL = useSsl;
            this._folders = new FolderCollection();
        }

        public Imap(string host, int port, bool useSsl, string userLogin, string userPassword)
        {
            this._delimiter = '/';
            base._imapHost = host;
            base._imapPort = port;
            base._useSSL = useSsl;
            base._userLogin = userLogin;
            base._userPassword = userPassword;
            this._folders = new FolderCollection();
        }

        public void CreateFolder(string name)
        {
            if ((this._imap == null) && !this._imap._isConnected)
            {
                throw new ImapException("Not Connect");
            }
            if (string.IsNullOrEmpty(name))
            {
                throw new ImapException("Not Set Folder Name");
            }
            string format = "CREATE \"{0}\"\r\n";
            ArrayList sResultArray = new ArrayList();
            string str2 = name;
            SendAndReceive(string.Format(format, str2));

            Folder folder = new Folder(name);
            folder.FolderPath = str2;
        }

        public FolderCollection GetFolders(string parent)
        {
            if ((this._imap == null) && !this._imap._isConnected)
            {
                throw new ImapException("Not Connect");
            }
            FolderCollection folders = new FolderCollection();
            ArrayList sResultArray = new ArrayList();
            string command = "LIST \"" + parent + "\" *\r\n";
            string result = SendAndReceive(command);

            int parentDepth = 0;
            if (parent != "")
            {
                parentDepth = parent.Split(_delimiter).GetLength(0);
            }

            Regex regex = new Regex(@"\* LIST \((?<flags>.*)\) ""(?<delim>.)"" ""(?<path>.*)""");

            foreach (Match match in regex.Matches(result))
            {
                _delimiter = match.Groups["delim"].Value[0];
                var flags = match.Groups["flags"].Value.ToLower();
                var path = match.Groups["path"].Value;
                var name = path.Substring(path.LastIndexOf(_delimiter) + 1);

                // if this isn't a direct child of the parent, skip it
                int depth = 0;
                if (path != "")
                {
                    depth = path.Split(_delimiter).GetLength(0);
                }
                if (depth != parentDepth + 1) continue;

                var folder = new Folder(name);
                folder.FolderPath = path;
                folder._client = this._imap;

                if (flags.Contains(@"\noselect"))
                {
                    folder._noselect = true;
                }

                if (flags.Contains(@"\haschildren"))
                {
                    folder._hasChildren = true;
                    folder.GetSubFolders();
                }

                folders.Add(folder);
            }
            return folders;
        }

        public MessageCollection SearchMessage(string path, Folder folder)
        {
            if ((this._imap == null) && !this._imap._isConnected)
            {
                throw new ImapException("Not Connect");
            }
            if (path == null)
            {
                throw new ImapException("Not Set Search Path");
            }
            string command = "UID SEARCH " + path + "\r\n";
            string result = SendAndReceive(command);

            Regex regex = new Regex(@"\*\s+SEARCH(\s+(?<messageuid>\d+))*");
            var match = regex.Match(result);
            MessageCollection messages = new MessageCollection();
            foreach (Capture capture in match.Groups["messageuid"].Captures)
            {
                string messageUID = capture.Value;
                int num = Convert.ToInt32(messageUID);
                Message item = new Message(this, num, folder);
                item.MessageUid = num;
                messages.Add(item);
            }
            return messages;
        }

        internal Imap _imap
        {
            get
            {
                return this;
            }
        }

        public string Fetch(int messageNum, string dataitem, bool isUID = false)
        {
            string command = string.Concat((isUID) ? "UID " : "", "FETCH ", messageNum, " (", dataitem, ")\r\n");
            string result = SendAndReceive(command);

            // * 1 FETCH (1 FLAGS (\\Seen) BODY[HEADER] {389}\r\n
            Match match = Regex.Match(result, @"\*\s+\d+\s+FETCH\s+\((UID\s+)?(?<seqNum>\d+)\s+(?<data>(.*\n)*)$");

            if (!match.Success) throw new ImapException("Unable to parse fetched result.");

            int seqNumResult = Convert.ToInt32(match.Groups["seqNum"].Value);
            Debug.Assert(seqNumResult == messageNum);

            return match.Groups["data"].Value;
        }

        /// <summary>
        /// Fetches a dataitem with the specified message UID.
        /// </summary>
        /// <param name="dataitem">The dataitem to fetch (ex: "FLAGS", "INTERNALDATE", "BODY")</param>
        /// <param name="uid">The message UID</param>
        /// <returns></returns>
        public string UidFetch(int uid, string dataitem)
        {
            return Fetch(uid, dataitem, true);
        }

        /// <summary>
        /// Fetches the body or part of the body of message specified by <paramref name="messageNum"/>.
        /// </summary>
        /// <param name="messageNum">The message UID</param>
        /// <param name="dataitem">The body data item to fetch (ex: "BODY", "BODY.PEEK[]", "BODY.PEEK[HEADER]")</param>
        /// <param name="isUID">Indicates if <paramref name="messageNum"/> is a UID or sequence number</param>
        /// <returns>The fetched body data</returns>
        public string FetchBody(int messageNum, string dataitem, bool isUID = false)
        {
            string result = Fetch(messageNum, dataitem, isUID);

            Match match = Regex.Match(result, @"(?<dataitem>[\w\[\]]*)\s+\{(?<dataLength>\d+)\}\r\n(?<data>(.*\n)*)$");

            if (!match.Success) throw new ImapException("Unable to parse fetched result");

            string dataitemResult = match.Groups["dataitem"].Value;

            return match.Groups["data"].Value;
        }

        public string UidFetchBody(int uid, string dataitem)
        {
            return FetchBody(uid, dataitem, true);
        }

        public void FetchDataItems(int uid, params DataItem[] args)
        {
            UidFetch command = new UidFetch(uid, args);
            ProcessCommand(command);
            //// get space separated list of dataitems
            //var dataitems = String.Join(" ", from arg in args select arg.Command);

            //// get regex pattern for dataitems
            //string pattern = String.Join(" ", from arg in args select arg.Pattern);

            //string result = UidFetch(uid, dataitems);

            //Match match = Regex.Match(result, pattern);

            //foreach (var dataitem in args)
            //{
            //    dataitem.Parse(match.Groups);
            //}
        }

        public void ProcessCommand(Command command)
        {
            string result = SendAndReceive(command.Command + "\r\n");
            command.Parse(result);
        }

        public int FetchBodySize(int messageNum, bool isUID = false)
        {
            string result = Fetch(messageNum, "RFC822.SIZE", isUID);

            Match match = Regex.Match(result, @"RFC822.SIZE\s(?<size>\d+)\)");

            if (!match.Success) throw new ImapException("Failed to parse fetched body size: '" + result + "'");

            return Convert.ToInt32(match.Groups["size"].Value);
        }

        public string FetchBodyInChunks(int messageNum, bool isUID = false)
        {
            const int chunksize = 100000;
            int chunkread;
            int read = 0;

            string result;
            string body = "";

            do
            {
                result = Fetch(messageNum, "BODY.PEEK[]<" + read.ToString() + "." + chunksize.ToString() + ">", isUID);

                Match match = Regex.Match(result, @"BODY\[\]\<(?<start>\d+)\>\s\{(?<size>\d+)\}\r\n(?<data>(.*\n)*)$");

                if (!match.Success) throw new ImapException("Failed to parse partial body peek");

                chunkread = Convert.ToInt32(match.Groups["size"].Value);
                int start = Convert.ToInt32(match.Groups["start"].Value);
                Debug.Assert(start == read);

                read += chunkread;
                body += match.Groups["data"].Value.Substring(0, chunkread);
            } while (chunkread == chunksize);

            return body;
        }

        public byte[] FetchBodyInChunks(int uid)
        {
            int tries = 3;
            byte[] buffer = null;

            while (tries > 0)
            {
                try
                {
                    const int chunksize = 100000;
                    int chunkread;
                    int read = 0;

                    buffer = null;

                    do
                    {
                        Array.Resize(ref buffer, read + chunksize);

                        string command = String.Concat("UID FETCH ", uid, " (BODY.PEEK[]<", read.ToString(), ".", chunksize.ToString() + ">)\r\n");
                        SendCommand(command);
                        string str = ReadLine();

                        Match match = Regex.Match(str, @"BODY\[\]\<(?<start>\d+)\>\s\{(?<size>\d+)\}");

                        if (!match.Success) throw new ImapException("Failed to parse partial body peek");

                        chunkread = Convert.ToInt32(match.Groups["size"].Value);
                        int start = Convert.ToInt32(match.Groups["start"].Value);
                        Debug.Assert(start == read);

                        int actual = 0;
                        while (true)
                        {
                            actual += streamReader.Read(buffer, read + actual, chunkread - actual);
                            if (actual == chunkread) break;
                            Thread.Sleep(1);
                        }

                        read += chunkread;
                        ReceiveResult();
                    } while (chunkread == chunksize);

                    Array.Resize(ref buffer, read);
                    return buffer;
                }
                catch (Exception)
                {
                    Reconnect();
                    tries--;
                    if (tries == 0) throw;
                }
            }
            throw new Exception("shouldn't get here");
        }

        public string FetchInternalDate(int messageNum, bool isUID = false)
        {
            string result = Fetch(messageNum, "INTERNALDATE", isUID);

            Match match = Regex.Match(result, @"INTERNALDATE\s+""(?<date>[^""]*)""");

            if (!match.Success) throw new ImapException("Failed to parse fetch of INTERNALDATE");

            return match.Groups["date"].Value;
        }

        const string flagsEx = @"FLAGS\s+\((?<flags>((?<flag>[\\\w]+)\s*))*\)";

        public string FetchFlags(int messageNum, bool isUID = false)
        {
            string result = Fetch(messageNum, "FLAGS", isUID);

            Match match = Regex.Match(result, flagsEx);

            if (!match.Success) throw new ImapException("Failed to parse fetch of FLAGS");

            return match.Groups["flags"].Value;
        }

        public int Append(string folderPath, byte[] data, string flags, string internalDate)
        {
            string command = string.Concat(new object[] { "APPEND \"", folderPath, "\" (", flags, ") \"", internalDate, "\" {", data.Length, "}\r\n" });

            SendAndReceive(command);
            string result2 = SendData(data);

            var match = Regex.Match(result2, @"\[APPENDUID (?<uidvalidity>\d+) (?<uid>\d+)\]");

            if (!match.Success) throw new ImapException("Could not parse APPENDUID - what up with that?");

            var uidvalidity = Convert.ToInt32(match.Groups["uidvalidity"].Value);
            return Convert.ToInt32(match.Groups["uid"].Value);
        }


    }
}

